<template>
    <div class="tnxel-edit-table-container" :id="id">
        <el-table ref="table"
            class="tnxel-edit-table"
            :class="{'padding-none': !padding}"
            :data="tableDataList"
            :row-key="rowKey"
            :row-class-name="tableRowClassName"
            :highlight-current-row="selectable"
            border
            @current-change="onSelectRow"
        >
            <template #empty v-if="!tableDataList">
                <tnxel-icon class="text-placeholder" value="Loading"/>
            </template>
            <slot></slot>
            <el-table-column label="排序" align="center" width="60px" v-if="sortable">
                <template #default="{row, $index}">
                    <tnxel-button
                        type="primary" link
                        icon="Top"
                        :class="{'text-transparent': !isSortableRow(row, $index,true)}"
                        @click="sortUp(row, $index)"
                    />
                    <tnxel-button
                        type="primary" link
                        icon="Bottom"
                        class="ms-0"
                        :class="{'text-transparent': !isSortableRow(row, $index, false)}"
                        @click="sortDown(row, $index)"
                    />
                </template>
            </el-table-column>
            <el-table-column label="移除" align="center" width="60px" v-if="removable">
                <template #default="{row, $index}">
                    <tnxel-button
                        type="primary" link
                        icon="CircleClose"
                        :tooltip="row.removeTip"
                        @click="removeRow(row, $index)"
                        v-if="isRemovableRow(row, $index)"/>
                </template>
            </el-table-column>
        </el-table>
        <div class="tnxel-edit-table-footer" v-if="addable">
            <tnxel-button type="primary" class="w-fit-content" link icon="Plus" @click="toAddRow">
                {{ addText }}
            </tnxel-button>
        </div>
        <tnxel-icon class="d-none icon-warning" value="WarningFilled"/>
    </div>
</template>

<script>
import Button from '../button/Button.vue';
import Icon from '../icon/Icon.vue';

export default {
    components: {
        'tnxel-button': Button,
        'tnxel-icon': Icon,
    },
    name: 'TnxelEditTable',
    props: {
        data: Array,
        addable: {
            type: Boolean,
            default: true,
        },
        removable: {
            type: [Boolean, Function],
            default: true,
        },
        addText: {
            type: String,
            default: '添加行',
        },
        newRow: {
            type: Function,
            default() {
                return () => {
                    return {};
                }
            }
        },
        rowKey: [Function, String],
        rowClassName: [Function, String],
        selectable: Boolean, // 可否选择行
        padding: Boolean, // 单元格中是否有边距
        sortable: [Boolean, Function], // 可否调整行的顺序
        rules: [String, Object], // 加载字段校验规则的URL地址，或规则集对象
        rulesApp: { // 加载字段校验规则的应用名称
            type: String,
            default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载，确保更改的值生效
        },
        rulesLoaded: Function, // 规则集加载后的附加处理函数，仅在rule为字符串类型的URL地址时有效
    },
    emits: ['sorted', 'removed'],
    data() {
        return {
            id: window.tnx.util.string.uuid32(),
            selectedRow: null,
            validationRules: {},
            indexes: {}, // 附加索引映射集，key：row，value：一级节点为单个索引下标，多级节点为其在各级节点中的索引下标集合
        }
    },
    computed: {
        tableDataList() {
            if (this.data) {
                for (let rowIndex = 0; rowIndex < this.data.length; rowIndex++) {
                    let row = this.data[rowIndex];
                    this.indexes[this.getRowKey(row, rowIndex)] = rowIndex;
                    if (this.rowKey && row.children) {
                        for (let j = 0; j < row.children.length; j++) {
                            let child = row.children[j];
                            this.indexes[this.getRowKey(child)] = [rowIndex, j];
                        }
                    }
                }
            }
            return this.data;
        },
    },
    watch: {
        data(newData, oldData) {
            if (!oldData) {
                this.$nextTick(() => {
                    this.initElements();
                });
            }
        },
        rules() {
            this.initRules();
        },
    },
    mounted() {
        if (this.selectable) {
            window.tnx.util.dom.replaceKeyEvent(document, () => {
                if (this.selectedRow) {
                    this.data.remove(record => record === this.selectedRow);
                }
            }, {key: 'Delete'});
        }
        this.$nextTick(() => {
            setTimeout(() => {
                this.initElements();
                this.initRules();
            });
        });
    },
    methods: {
        getRowKey(row, rowIndex) {
            return this.rowKey ? row[this.rowKey] : (rowIndex + '');
        },
        initElements() {
            if (this.tableDataList) {
                this.focusRowFirstInput(this.tableDataList[0], 0);
                // 为富文本输入框添加默认title
                let table = this.$refs.table?.$el;
                if (table) {
                    let textareas = table.getElementsByTagName('textarea');
                    for (let textarea of textareas) {
                        if (!textarea.title) {
                            textarea.addEventListener('input', event => {
                                event.target.title = event.target.value;
                            });
                        }
                    }
                }
            }
        },
        initRules() {
            if (this.rules) {
                if (typeof this.rules === 'string') {
                    window.tnx.app.rpc.getMeta(this.rules, this.rulesApp).then(meta => {
                        if (this.rulesLoaded) {
                            this.rulesLoaded(meta.$rules);
                        } else {
                            this.$emit('rules-loaded', meta.$rules);
                        }
                        this.validationRules = meta.$rules;
                        this.bindRules();
                    });
                } else {
                    this.validationRules = this.rules;
                    this.bindRules();
                }
            } else {
                this.validationRules = {};
                this.bindRules();
            }
        },
        loopColumns(consumer) {
            let columns = this.$slots.default();
            if (Array.isArray(columns)) {
                let containers = document.getElementsByClassName('tnxel-edit-table-container');
                for (let container of containers) {
                    if (container.id === this.id) {
                        let cols = container.querySelectorAll('.el-table__header colgroup col');
                        if (cols.length >= columns.length) {
                            for (let i = 0; i < columns.length; i++) {
                                let column = columns[i];
                                let props = column.props;
                                let fieldName = props.prop;
                                let columnName = cols[i].getAttribute('name');
                                consumer(fieldName, columnName);
                            }
                            return true;
                        }
                        break;
                    }
                }
            }
            return false;
        },
        loopValidatableElements(consumer) {
            this.loopColumns((fieldName, columnName) => {
                if (this.validationRules[fieldName]) {
                    let tds = this.$refs.table.$el.getElementsByClassName(columnName);
                    for (let td of tds) {
                        let elements = td.getElementsByTagName('input');
                        if (elements.length === 0) {
                            elements = td.getElementsByTagName('textarea');
                        }
                        for (let element of elements) {
                            consumer(fieldName, element);
                        }
                    }
                }
            });
        },
        bindRules() {
            this.bindElementRules();
            this.loopColumns((fieldName, columnName) => {
                let rules = this.validationRules[fieldName];
                if (rules) {
                    for (let rule of rules) {
                        if (rule.required) {
                            let th = this.$refs.table.$el.querySelector('th.' + columnName);
                            if (th) {
                                th.classList.add('is-required');
                            }
                            break;
                        }
                    }
                }
            });
        },
        bindElementRules() {
            this.loopValidatableElements((fieldName, element) => {
                let blurEventHandler = event => {
                    this.validateElement(fieldName, event.target);
                };
                element.removeEventListener('blur', blurEventHandler);
                element.addEventListener('blur', blurEventHandler);
                let focusEventHandler = event => {
                    this.removeErrorIcon(event.target);
                };
                element.removeEventListener('focus', focusEventHandler);
                element.addEventListener('focus', focusEventHandler);
            });
        },
        validateElement(fieldName, element) {
            this.removeErrorIcon(element);
            element.parentElement.classList.remove('is-error');

            let rules = {};
            rules[fieldName] = this.validationRules[fieldName];
            let model = {};
            model[fieldName] = element.value;
            let successful = true;
            window.tnx.app.validator.validate(rules, model).catch(errors => {
                if (errors) {
                    successful = false;
                    let message = '';
                    for (let error of errors) {
                        message += error.message + '\n';
                    }
                    message = message.trim();
                    element.parentElement.classList.add('is-error');

                    let container = document.getElementById(this.id);
                    let icon = container.getElementsByClassName('icon-warning')[0];
                    icon = icon.cloneNode(true);
                    icon.classList.remove('d-none');
                    icon.classList.remove('icon-warning');
                    icon.classList.add('error-icon');
                    icon.classList.add('text-danger');
                    icon.title = message;
                    icon.style.top = (element.parentElement.offsetHeight - 16) / 2 + 'px';
                    element.parentElement.appendChild(icon);
                }
            });
            return successful;
        },
        validateTable() {
            let successful = true;
            this.loopValidatableElements((fieldName, element) => {
                let validated = this.validateElement(fieldName, element);
                successful = validated && successful;
            });
            return successful;
        },
        removeErrorIcon(inputElement) {
            let sibling = inputElement.nextSibling;
            while (sibling) {
                if (sibling.classList && sibling.classList.contains('error-icon')) {
                    sibling.remove();
                }
                sibling = sibling.nextSibling;
            }
        },
        onSelectRow(row) {
            if (this.selectable) {
                this.selectedRow = row;
            }
        },
        tableRowClassName(data) {
            let classNames = [this.getRowClassName(data.row, data.rowIndex)];
            if (typeof this.rowClassName === 'function') {
                classNames.push(this.rowClassName(data));
            } else if (this.rowClassName) {
                classNames.push(this.rowClassName);
            }
            return classNames.join(' ');
        },
        getRowClassName(row, rowIndex) {
            let index = this.indexes[this.getRowKey(row, rowIndex)];
            return 'tnxel-edit-table__row--' + (Array.isArray(index) ? index.join('_') : index);
        },
        focusRowFirstInput(row, rowIndex) {
            if (row && this.$refs.table) {
                let table = this.$refs.table.$el;
                let rowClassName = this.getRowClassName(row, rowIndex);
                let rowDoms = table.getElementsByClassName(rowClassName);
                if (rowDoms.length) {
                    let rowDom = rowDoms[0];
                    if (rowDom) {
                        let cells = rowDom.getElementsByClassName('cell');
                        for (let cell of cells) {
                            let input = cell.querySelector('input:first-child');
                            if (input) {
                                input.focus();
                                return true;
                            } else {
                                let textarea = cell.querySelector('textarea:first-child');
                                if (textarea) {
                                    textarea.focus();
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
            return false;
        },
        toAddRow() {
            let row = this.newRow();
            if (row) {
                let rowIndex = this.data.length;
                this.indexes[this.getRowKey(row, rowIndex)] = rowIndex;
                this.data.push(row);
                this.$nextTick(() => {
                    setTimeout(() => {
                        this.bindElementRules();
                        this.focusRowFirstInput(row, rowIndex);
                    });
                });
            }
        },
        isRemovableRow(row, rowIndex) {
            if (typeof this.removable === 'function') {
                let index = this.indexes[this.getRowKey(row, rowIndex)];
                let removable = this.removable(row, index);
                if (removable !== undefined) {
                    return removable;
                }
                // 自定义函数如果返回undefined，则使用默认判断逻辑
            }
            return !!this.removable;
        },
        removeRow(row, rowIndex) {
            this.locateRow(row, rowIndex, (dataList, index) => {
                delete this.indexes[this.getRowKey(row, rowIndex)];
                dataList.splice(index, 1);
                this.$emit('removed', row, rowIndex);
                this.$nextTick(() => {
                    setTimeout(() => {
                        let next = dataList[index];
                        let nextIndex = rowIndex;
                        if (!next) {
                            next = dataList[index - 1];
                            nextIndex = rowIndex - 1;
                        }
                        this.focusRowFirstInput(next, nextIndex);
                    });
                });
            });
        },
        locateRow(row, rowIndex, func) {
            if (typeof rowIndex === 'function') {
                func = rowIndex;
                rowIndex = undefined;
            }
            let indexes = this.indexes[this.getRowKey(row, rowIndex)];
            if (!Array.isArray(indexes)) {
                indexes = [indexes];
            }
            let dataList = this.tableDataList;
            for (let i = 0; i < indexes.length - 1; i++) {
                dataList = dataList[indexes[i]].children;
            }
            let index = indexes[indexes.length - 1];
            return func(dataList, index);
        },
        isSortableRow(row, rowIndex, up) {
            if (typeof this.sortable === 'function') {
                let index = this.indexes[this.getRowKey(row, rowIndex)];
                let sortable = this.sortable(row, index, up);
                if (sortable !== undefined) {
                    return sortable;
                }
                // 自定义函数如果返回undefined，则使用默认判断逻辑
            }
            if (this.sortable) {
                if (up) {
                    let index = this.indexes[this.getRowKey(row, rowIndex)];
                    if (Array.isArray(index)) {
                        index = index[index.length - 1];
                    }
                    return index > 0;
                } else {
                    return this.locateRow(row, rowIndex, (dataList, index) => {
                        return index < dataList.length - 1;
                    });
                }
            }
            return false;
        },
        sortUp(row, rowIndex) {
            this.locateRow(row, rowIndex, (dataList, index) => {
                if (index > 0) {
                    let deleted = dataList.splice(index, 1);
                    dataList.splice(index - 1, 0, deleted[0]);
                    this.$emit('sorted', row, rowIndex, true);
                }
            });
        },
        sortDown(row, rowIndex) {
            this.locateRow(row, rowIndex, (dataList, index) => {
                if (index < dataList.length - 1) {
                    let deleted = dataList.splice(index, 1);
                    dataList.splice(index + 1, 0, deleted[0]);
                    this.$emit('sorted', row, rowIndex, false);
                }
            });
        },
        toggleRowExpansion(row, expanded) {
            this.$refs.table.toggleRowExpansion(row, expanded);
        },
    }
}
</script>

<style>
.tnxel-edit-table-container .el-table .el-table__row {
    height: 33px;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-table__cell,
.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper td .cell,
.tnxel-edit-table-container .el-table--border.padding-none .el-table__body-wrapper td.el-table__cell:first-child .cell {
    padding: 0;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input__wrapper,
.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-select__wrapper {
    border-radius: 0;
    height: 32px;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input-group__append,
.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input-group__prepend {
    border-radius: 0;
    padding: 0 10px;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-button {
    border-radius: 0;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-input__wrapper,
.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-select__wrapper {
    height: 24px;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-textarea__wrapper {
    border-radius: 0;
    height: 40px;
}

.tnxel-edit-table-container .el-table.padding-none .el-table__body-wrapper .el-input--small .el-textarea__wrapper {
    height: 32px;
}

.tnxel-edit-table-container .tnxel-edit-table .is-center .el-input__inner {
    text-align: center;
}

.tnxel-edit-table-container .tnxel-edit-table .is-right .el-input__inner {
    text-align: right;
}

.tnxel-edit-table-container .tnxel-edit-table .el-cascader .el-input__inner {
    text-align: left;
}

.tnxel-edit-table-container .el-table .cell {
    display: flex;
    align-items: center;
}

.tnxel-edit-table-container .el-table .is-center .cell {
    justify-content: center;
}

.tnxel-edit-table-container .el-table .el-table__placeholder {
    width: 20px;
}

.tnxel-edit-table-container .el-table .el-table__indent + .el-table__placeholder {
    width: 16px;
}

.tnxel-edit-table-container .el-table .is-center .el-table__placeholder {
    width: 16px;
}

.tnxel-edit-table-container .el-table .is-center .el-table__indent + .el-table__placeholder {
    width: 24px;
}

.tnxel-edit-table-container .el-table [class*=el-table__row--level] .el-table__expand-icon {
    margin-left: 4px;
    margin-right: 4px;
}

.tnxel-edit-table-container .el-table [class*=el-table__row--level] .is-center .el-table__expand-icon {
    margin-left: 0;
}

.tnxel-edit-table-container .tnxel-edit-table .el-table__expand-icon + div {
    width: auto;
    flex-grow: 1;
}

.tnxel-edit-table-container .el-table .is-error {
    box-shadow: 0 0 0 1px var(--el-color-danger) inset;
}

.tnxel-edit-table-container .el-table th.is-required:not(.is-no-asterisk) .cell:after {
    content: "*";
    color: var(--el-color-danger);
    margin-left: 4px;
}

.tnxel-edit-table-container .el-table .is-error .error-icon {
    position: absolute;
    top: 12px;
    right: 10px;
    font-size: 16px;
    cursor: default;
}

.tnxel-edit-table-container .el-table .el-input__inner:focus + .error-icon,
.tnxel-edit-table-container .el-table .el-textarea__inner:focus + .error-icon {
    display: none;
}

.tnxel-edit-table-container .el-table .el-button.text-transparent {
    cursor: default;
}

.tnxel-edit-table-container .el-table .disabled {
    cursor: not-allowed;
}

.tnxel-edit-table-container .tnxel-edit-table-footer {
    border-left: 1px solid var(--el-border-color-lighter);
    border-right: 1px solid var(--el-border-color-lighter);
    border-bottom: 1px solid var(--el-border-color-lighter);
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.tnxel-edit-table-container .tnxel-edit-table-footer .el-button {
    width: 100%;
}
</style>
